/*********************************************************************************
 * 
 *        Class: Mainline
 * 
 *       Author: ©2009 Tyler Beck (http://www.tylerbeck.com)
 * 
 *  Description: Mainline Framework foundation singleton class
 * 
 *    IMPORTANT: "-keep-as3-metadata=Inject" must be added to 
 * 				 additional compiler settings
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see http://www.gnu.org/licenses/.
 *********************************************************************************/

package com.tylerbeck.mainline
{

	// IMPORTS ###################################################################
	import com.tylerbeck.mainline.commands.Command;
	import com.tylerbeck.mainline.dispatchers.MainlineDispatcher;
	import com.tylerbeck.mainline.events.CommandEvent;
	import com.tylerbeck.mainline.events.MainlineEvent;
	import com.tylerbeck.mainline.injectors.IObjectInjector;
	import com.tylerbeck.mainline.logging.AS3Logger;
	import com.tylerbeck.mainline.logging.IMainlineLogger;
	import com.tylerbeck.mainline.registries.IRegistry;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	import flash.utils.describeType;
		
	//############################################################################
	
	public class Mainline extends MainlineDispatcher
	{
		//========================================================================
		// STATIC ATTRIBUTES
		//========================================================================
		private static var instance:Mainline = null;
		
		public static var LOG_LEVEL_COMMAND_CONSTRUCTOR:int = 1;
		public static var LOG_LEVEL_COMMAND_START:int       = 4;
		public static var LOG_LEVEL_COMMAND_COMPLETE:int    = 3;
		public static var LOG_LEVEL_COMMAND_ERROR:int       = 3;
		public static var LOG_LEVEL_LOAD_REGISTRY:int       = 3;
		public static var LOG_LEVEL_LOAD_REGISTRY_ITEMS:int = 2;
		public static var LOG_LEVEL_MAINLINE_INITIALIZE:int = 1;
		public static var LOG_LEVEL_DISPATCH_EVENT:int      = 2;
		public static var LOG_LEVEL_EXECUTE_COMMAND:int     = 2;
		public static var LOG_LEVEL_INTROSPECT:int          = 2;

		//========================================================================
		// ATTRIBUTES
		//========================================================================
		private var initialized : Boolean = false;
		
		protected var registries : Array;
		protected var injectors : Dictionary;
		protected var events : Dictionary;
		
		//logging variables
		public var logEnabled : Boolean = false;
		public var logger:IMainlineLogger;

		//========================================================================
		// CONSTRUCTOR
		//========================================================================
		public function Mainline()
		{
			super();
			initLogging();

			if ( instance != null)
				throw new Error("Only one instance of the Mainline framework can be instantiated!");
				
			injectors = new Dictionary();
			events = new Dictionary();
			dispatcher = new EventDispatcher(this);
			registries = new Array();
			
		}
		
		//========================================================================
		// FUNCTIONS
		//========================================================================		
		//------------------------------------------------------------------------
		// loadRegistry - adds / loads registry's - this function should be called
		// 				  by all IRegistry Classes
		//------------------------------------------------------------------------
		public function loadRegistry( registry:IRegistry ) : void
		{
			log('loadRegistry: '+registry,instance, Mainline.LOG_LEVEL_LOAD_REGISTRY);
			if (!initialized)
				registries.push(registry);
			else
				loadRegistryItems(registry);
		}
				
		//------------------------------------------------------------------------
		// unloadRegistry - removes / unloads registry's
		//------------------------------------------------------------------------
		public function unloadRegistry( registry:IRegistry ) : void
		{
			log('unloadRegistry: '+registry,this, Mainline.LOG_LEVEL_LOAD_REGISTRY);
			for each(var item:Object in registry.items)
			{
				//default type should be injector
				if (!item.hasOwnProperty('type'))
					item.type = 'injector';
					
				//load
				switch (item.type)
				{
					case 'injector':
						var id:String = item.id;
						injectors[id] = null;
						break;
					case 'command':
						var name:String = item.name;
						var command:Class = item.command;
						events[name] = null;						
						if (command != null)
							dispatcher.removeEventListener(name,executeCommand);
						break;
				}
			}
		}
				
		//------------------------------------------------------------------------
		// loadRegistryItems - adds / loads registry's injectors
		//------------------------------------------------------------------------
		protected function loadRegistryItems( registry:IRegistry ) : void
		{		
			log('loadRegistryItems: '+registry,this, Mainline.LOG_LEVEL_LOAD_REGISTRY_ITEMS);
			for each(var item:Object in registry.items)
			{
				//default type should be injector
				if (!item.hasOwnProperty('type'))
					item.type = 'injector';
					
				//load
				switch (item.type)
				{
					case 'injector':
						log('add injector:'+item.id,this);
						var id:String = item.id;
						var injector:IObjectInjector = item.injector;
						if (injectors[id] != null)
							throw new Error("The dependancy ID '"+id+"' has already been taken");
						injectors[id] = injector;
						break;
					case 'command':
						log('add command:'+item.name,this);
						var name:String = item.name;
						var command:Class = item.command;
						if (events[name] != null)
							throw new Error("Event name '"+name+"' has already been taken");
						
						events[name] = {name:name,command:command};
						if (command != null)
							dispatcher.addEventListener(name,executeCommand);
						break;
				}
			}
		}	
		
		//------------------------------------------------------------------------
		// initialize - sets up loaded registries - sets up initial Mainline registries
		//------------------------------------------------------------------------
		public function initialize(event:Event = null) : void
		{			
			log('initialize',this, Mainline.LOG_LEVEL_MAINLINE_INITIALIZE);
			registries.sortOn('priority', Array.NUMERIC);
			for each( var registry:IRegistry in registries )
			{
				loadRegistryItems(registry);
			}
		}
		
		//------------------------------------------------------------------------
		// executeCommand - executes a event command
		//------------------------------------------------------------------------
		protected function executeCommand( event:MainlineEvent ) : void
		{
			log('executeCommand: '+event.type,this, Mainline.LOG_LEVEL_EXECUTE_COMMAND);
			var commandClass:Class = events[event.type].command;
			var command:Command =  new commandClass();
			if ( command != null )
			{
				if (event.completeCallback != null)
					command.addEventListener(CommandEvent.COMPLETE,event.completeCallback);
				if (event.errorCallback != null)
					command.addEventListener(CommandEvent.ERROR,event.errorCallback);
				command.execute(event.parameters);
			}
			else 
				throw new Error('method "execute" not found on '+events[event.type]);
		}
		
		//------------------------------------------------------------------------
		// dispatch - dispatches a MainlineEvent
		//------------------------------------------------------------------------
		public function dispatch( type:String, paramObj:Object = null, completeCallback:Function = null, errorCallback:Function = null  ) : void
		{
			log('dispatch: '+type,this, Mainline.LOG_LEVEL_DISPATCH_EVENT);
			var event:MainlineEvent = new MainlineEvent( type, paramObj, completeCallback, errorCallback );
			dispatcher.dispatchEvent(event);
		}		
					
		//------------------------------------------------------------------------
		// introspect - examines object and does injections
		//------------------------------------------------------------------------
		public function introspect( obj:Object ) : void
		{
			log('introspect',obj, Mainline.LOG_LEVEL_INTROSPECT);
			var objDescription:XML = describeType( obj );

			/** get the objects that need to be injected */
			var variables : XMLList = objDescription.variable.(child('metadata').(@name=='Inject').length() > 0);
			var accessors : XMLList = objDescription.accessor.(child('metadata').(@name=='Inject').length() > 0).(@access=='readwrite' || @access=='write');
						
			doInjections(obj,variables,accessors);

		}

		//------------------------------------------------------------------------
		// doInjections - do injections for descriptors
		//------------------------------------------------------------------------
		protected function doInjections( obj:Object, ...lists ) : void
		{
			for each (var list:XMLList in lists)
			{
				for (var i:String in list)
				{
					var item:XML = list[i];
					var property:String = item.@name;
					var injection:XML = item.metadata.(@name=='Inject')[0];
					
					/** only inject if argument is available */
					var id:String = (injection.hasOwnProperty('arg')) ? (injection.arg[0].@value) : ('');
					if (id != '')
					{
						/** do the injection */
						if (injectors[id] != null)
							injectors[id].inject( obj, property );
					}
				}
			}
		}
				
		//------------------------------------------------------------------------
		// getValue - return injector value
		//------------------------------------------------------------------------
		public function getInjectorValue( injectorId:String ) : *
		{
			log('getInjectorValue: '+injectorId,this, Mainline.LOG_LEVEL_INTROSPECT);
			var obj:Object = {prop:null};
			if (injectors[injectorId] != null)
					injectors[injectorId].inject( obj, 'prop' );
			return obj['prop'];
		}
		
		//------------------------------------------------------------------------
		// initLogging - 
		//------------------------------------------------------------------------
		protected function initLogging():void 
		{
            if (logger == null)
            {
	            // Create a logger.
	            logger = new AS3Logger();
	        }
            
            // Begin logging.
 			log('BEGIN LOGGING',this);
        }

		//------------------------------------------------------------------------
		// log - 
		//------------------------------------------------------------------------
		public function log(msg:String = '',target:Object = null, level:int = 3) : void
		{
			if (logEnabled)
			{
				logger.log(msg,target,level);
			}
		}
		

		// STATIC FUNCTION ALIASES ###############################################
		//------------------------------------------------------------------------
		// getInstance - gets singleton instance
		//------------------------------------------------------------------------
		public static function getInstance() : Mainline
		{
			if (instance == null)
			{
				instance = new Mainline();
			}
			return instance;
		}
		
		public static function initialize( event:Event = null) : void { Mainline.getInstance().initialize( event ) }
		public static function introspect( obj:Object ) : void { Mainline.getInstance().introspect( obj ) }
		public static function inject( event:Event ) : void { Mainline.getInstance().introspect( event.currentTarget ) }
		public static function getInjectorValue( injectorId:String ) : * { return Mainline.getInstance().getInjectorValue( injectorId ) }
		public static function loadRegistry( registry:IRegistry ) : void { Mainline.getInstance().loadRegistry( registry ) }
		public static function unloadRegistry( registry:IRegistry ) : void { Mainline.getInstance().unloadRegistry( registry ) }
		public static function dispatch( type:String, paramObj:Object = null, completeCallback:Function = null, errorCallback:Function = null ) : void { Mainline.getInstance().dispatch( type, paramObj, completeCallback, errorCallback ) }
		public static function addEventListener( type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false ) : void { Mainline.getInstance().addEventListener( type, listener, useCapture, priority, useWeakReference ); }
		public static function removeEventListener( type:String, listener:Function, useCapture:Boolean=false ) : void { Mainline.getInstance().removeEventListener( type, listener, useCapture ); }
		public static function dispatchEvent( event:Event ) : Boolean { return Mainline.getInstance().dispatchEvent( event ); }
		public static function log( msg:String, target:Object = null, level:int = 2 ) : void { return Mainline.getInstance().log( msg, target, level ); }
		public static function set logEnabled( value:Boolean ) : void { Mainline.getInstance().logEnabled = value; }
		public static function get logEnabled() : Boolean { return Mainline.getInstance().logEnabled; }
		public static function set logger( value:IMainlineLogger ) : void { Mainline.getInstance().logger = value; }
		public static function get logger() : IMainlineLogger { return Mainline.getInstance().logger; }
		
	}
}